# -*- coding: utf-8 -*-
import os
from md5 import md5
from threading import local

import paths
import file_opts

class HasBeenLockedError(Exception):pass

class FileLocker(local):
    def __init__(self, lockerName, expiredSeconds=0):
        lockerName = repr(lockerName)
        strmd5 = md5(lockerName).hexdigest()
        lockerFolder = os.path.join(paths.FILE_LOCKER, strmd5[:2])
        self.__lockerFilePath = os.path.join(lockerFolder, strmd5)
        self.__expiredSeconds = expiredSeconds
        self.__file = None
        self.__isLocked = False
        file_opts.ensure_dir_exists(lockerFolder)

    def lock(self, breakOnFailed = False):
        self.__file = file(self.__lockerFilePath, "w")
        self.__lock(breakOnFailed)
        self.__isLocked = True
        if self.__expiredSeconds > 0:
            import threading
            t = threading.Thread(target = self.__daemonMethod)
            t.setDaemon(True)
            t.start()

    acquire = lock
    
    __enter__ = acquire
    
    def unlock(self):
        if not self.__isLocked: return
        self.__isLocked = False
        if not self.__file: return
        self.__unlock()
        self.__file.close()
        self.__file = None
    release = unlock
    
    def __exit__(self, typ, val, tb):
        self.release()
    
    def __del__(self):
        try:
            self.unlock()
        except:
            pass
    
    def __daemonMethod(self):
        waitSeconds = 0
        import time
        while self.__isLocked and waitSeconds < self.__expiredSeconds:
            waitSeconds += 2
            time.sleep(2)
        if self.__isLocked:
            self.unlock()

    def __lock(self, breakOnFailed):
        lockType = fcntl.LOCK_EX
        if breakOnFailed:
            lockType = lockType | fcntl.LOCK_NB
        try:
            fcntl.flock(self.__file.fileno(), lockType)
        except IOError, e:
            self.__file.close()
            self.__file = None
            import errno
            if e.errno == errno.EACCES or e.errno == errno.EAGAIN:
                raise HasBeenLockedError("Locker has been acquired before")
            raise
    
        def __unlock(self):
            fcntl.flock(self.__file.fileno(), fcntl.LOCK_UN)
    